#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <cstring>
#include <utility>
#include <initializer_list>
#include <iostream>
#include <functional>
#include "async_driver.hpp"
#include <iostream>
#define SERVER_PORT 12345

#define TRUE 1
#define FALSE 0

auto PollDriver::WithIPV6(bool with /* = true */) -> PollDriver::Option
{
    return [with](PollDriver *driver)
    {
        if (driver != nullptr && with)
        {
            driver->_opts._socket_domain = AF_INET6;
        }
    };
}

auto PollDriver::WithIPV4() -> PollDriver::Option
{
    return [](PollDriver *driver)
    {
        if (driver != nullptr)
        {
            driver->_opts._socket_domain = AF_INET;
        }
    };
}

auto PollDriver::WithExceptions(bool with /* = true */) -> PollDriver::Option
{
    return [with](PollDriver *driver)
    {
        if (driver != nullptr)
        {
            driver->_opts._with_exceptions = with;
        }
    };
}

auto PollDriver::WithReusable(bool with /* = true */) -> PollDriver::Option
{
    return [with](PollDriver *driver)
    {
        if (driver != nullptr)
        {
            driver->_opts._reusable = with;
        }
    };
}

auto PollDriver::WithInitFDSize(std::size_t size /* = 10 */) -> PollDriver::Option
{
    return [size](PollDriver *driver)
    {
        if (driver != nullptr)
        {
            driver->_opts._initial_poll_fds_size = size;
        }
    };
}

int PollDriver::check() const
{
    return _error;
}

PollDriver::PollDriver(std::initializer_list<Option> opts)
{
    _applyOptions(opts);
    _createListenFD();
    _setReusable();
    _setNonBlocking();
    _bind();
    _listen();
    _initPoll();
}

PollDriver::~PollDriver()
{
    _closeListener();
}

void PollDriver::_applyOptions(std::initializer_list<Option> &opts)
{
    for (auto &opt : opts)
    {
        opt(this);
    }
    if (auto err = _opts.check(); err)
    {
        _checkOrThrow(err);
    }
}

void PollDriver::_createListenFD()
{
    _listen_fd = socket(_opts._socket_domain, SOCK_STREAM, 0);
    if (_listen_fd < 0)
    {
        _checkOrThrow(-1);
    }
}

void PollDriver::_setReusable()
{
    if (_error)
    {
        return;
    }
    if (_opts._reusable)
    {
        int rc = setsockopt(_listen_fd, SOL_SOCKET, SO_REUSEADDR,
                            (char *)&_opts._reusable, sizeof(_opts._reusable));
        if (rc < 0)
        {
            _closeListener();
            _checkOrThrow(-1);
        }
    }
}

void PollDriver::_setNonBlocking()
{
    if (_error)
    {
        return;
    }
    int nonblocking = 1;
    int rc = ioctl(_listen_fd, FIONBIO, (char *)&nonblocking);
    if (rc < 0)
    {
        _closeListener();
        _checkOrThrow(-1);
    }
}

void PollDriver::_bind()
{
    std::memset(&_addr.in6, 0, sizeof(_addr.in6));
    _addr.in6.sin6_family = AF_INET6;
    std::memcpy(&_addr.in6.sin6_addr, &in6addr_any, sizeof(in6addr_any));
    _addr.in6.sin6_port = htons(SERVER_PORT);
    int rc = bind(_listen_fd,
                  (struct sockaddr *)&_addr.in6, sizeof(_addr.in6));
    if (rc < 0)
    {
        _closeListener();
        _checkOrThrow(-1);
    }
}

void PollDriver::_listen()
{
    int rc = listen(_listen_fd, 32);
    if (rc < 0)
    {
        _closeListener();
        _checkOrThrow(-1);
    }
}

void PollDriver::_initPoll()
{
    _fds.reserve(_opts._initial_poll_fds_size);
    struct pollfd listen = {0};
    listen.fd = _listen_fd;
    listen.events = POLLIN;
    _fds.emplace_back(listen);
}

Requests PollDriver::Poll() noexcept
{
    do
    {
        int rc = ::poll(_fds.data(), _fds.size(), _opts._poll_timeout);
        if (rc == 0)
        {
            continue;
        }
        std::cout << "deb -- _fds.size: " << _fds.size() << " cap: " << _fds.capacity()
                  << " adr: " << _fds.data() << std::endl;
        if (rc < 0)
        {
            _closeListener();
            _checkOrThrow(-1);
            return {};
        }
        for (auto i = 0; i < _fds.size(); ++i)
        {
            if (_fds[i].revents == 0)
            {
                continue;
            }
            if (_fds[i].revents != POLLIN)
            {
                // foobar
                return {};
            }
            if (_fds[i].fd == _listen_fd)
            { // new conn
                int new_fd = -1;
                do
                {
                    new_fd = ::accept(_listen_fd, nullptr, nullptr);
                    if (new_fd < 0)
                    {
                        if (errno != EWOULDBLOCK)
                        {
                            // foobar
                            return {};
                        }
                        break;
                    }
                    struct pollfd newConn = {0};
                    newConn.fd = new_fd;
                    newConn.events = POLLIN;
                    _fds.emplace_back(newConn);
                } while (new_fd != -1);
            }
            else
            {
                do
                {
                    std::array<char, 1024> buffer = {0};
                    int rc = recv(_fds[i].fd, buffer.data(), buffer.size(), 0);
                    if (rc < 0)
                    {
                        if (errno != EWOULDBLOCK)
                        {
                            // foobar
                            return {};
                        }
                        break;
                    }
                    if (rc == 0)
                    {
                        // close
                        _closeClient(i);
                        std::cout << "close" << std::endl;
                        break;
                    }
                    std::cout << std::string{buffer.data()} << std::endl;
                } while (true);
            }
        }
    } while (true);
}

void PollDriver::_closeListener()
{
    if (_listen_fd != -1)
    {
        close(_listen_fd);
        _listen_fd = -1;
    }
}

void PollDriver::_closeClient(std::size_t idx)
{
    if (0 < idx && idx < _fds.size())
    {
        ::close(_fds[idx].fd);
        _fds[idx].fd = -1;
        if (idx > 1)
        {
            std::swap(_fds[idx], _fds[_fds.size() - 1]);
        }
        _fds.resize(_fds.size() - 1);
    }
}

void PollDriver::_checkOrThrow(int error)
{
    _error = error;
    if (_opts._with_exceptions)
    {
        throw _error;
    }
}

int main(int argc, char *argv[])
{
    try
    {
        PollDriver d{
            PollDriver::WithIPV6(),
            PollDriver::WithReusable(),
            PollDriver::WithExceptions(),
            PollDriver::WithInitFDSize(10)};
        d.Poll();
    }
    catch (int a)
    {
        std::cout << a << std::endl;
    }
}

// int f()
// {
//     int len, rc, on = 1;
//     int listen_sd = -1, new_sd = -1;
//     int desc_ready, end_server = FALSE, compress_array = FALSE;
//     int close_conn;
//     char buffer[80];
//     struct sockaddr_in6 addr;
//     int timeout;
//     struct pollfd fds[200];
//     int nfds = 1, current_size = 0, i, j;

//     /*************************************************************/
//     /* Create an AF_INET6 stream socket to receive incoming      */
//     /* connections on                                            */
//     /*************************************************************/
//     listen_sd = socket(AF_INET6, SOCK_STREAM, 0);
//     if (listen_sd < 0)
//     {
//         perror("socket() failed");
//         exit(-1);
//     }

//     /*************************************************************/
//     /* Allow socket descriptor to be reuseable                   */
//     /*************************************************************/
//     rc = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR,
//                     (char *)&on, sizeof(on));
//     if (rc < 0)
//     {
//         perror("setsockopt() failed");
//         close(listen_sd);
//         exit(-1);
//     }

//     /*************************************************************/
//     /* Set socket to be nonblocking. All of the sockets for      */
//     /* the incoming connections will also be nonblocking since   */
//     /* they will inherit that state from the listening socket.   */
//     /*************************************************************/
//     rc = ioctl(listen_sd, FIONBIO, (char *)&on);
//     if (rc < 0)
//     {
//         perror("ioctl() failed");
//         close(listen_sd);
//         exit(-1);
//     }

//     /*************************************************************/
//     /* Bind the socket                                           */
//     /*************************************************************/
//     memset(&addr, 0, sizeof(addr));
//     addr.sin6_family = AF_INET6;
//     memcpy(&addr.sin6_addr, &in6addr_any, sizeof(in6addr_any));
//     addr.sin6_port = htons(SERVER_PORT);
//     rc = bind(listen_sd,
//               (struct sockaddr *)&addr, sizeof(addr));
//     if (rc < 0)
//     {
//         perror("bind() failed");
//         close(listen_sd);
//         exit(-1);
//     }

//     /*************************************************************/
//     /* Set the listen back log                                   */
//     /*************************************************************/
//     rc = listen(listen_sd, 32);
//     if (rc < 0)
//     {
//         perror("listen() failed");
//         close(listen_sd);
//         exit(-1);
//     }

//     /*************************************************************/
//     /* Initialize the pollfd structure                           */
//     /*************************************************************/
//     memset(fds, 0, sizeof(fds));

//     /*************************************************************/
//     /* Set up the initial listening socket                        */
//     /*************************************************************/
//     fds[0].fd = listen_sd;
//     fds[0].events = POLLIN;
//     /*************************************************************/
//     /* Initialize the timeout to 3 minutes. If no                */
//     /* activity after 3 minutes this program will end.           */
//     /* timeout value is based on milliseconds.                   */
//     /*************************************************************/
//     timeout = (3 * 60 * 1000);

//     /*************************************************************/
//     /* Loop waiting for incoming connects or for incoming data   */
//     /* on any of the connected sockets.                          */
//     /*************************************************************/
//     do
//     {
//         /***********************************************************/
//         /* Call poll() and wait 3 minutes for it to complete.      */
//         /***********************************************************/
//         printf("Waiting on poll()...\n");
//         rc = poll(fds, nfds, timeout);

//         /***********************************************************/
//         /* Check to see if the poll call failed.                   */
//         /***********************************************************/
//         if (rc < 0)
//         {
//             perror("  poll() failed");
//             break;
//         }

//         /***********************************************************/
//         /* Check to see if the 3 minute time out expired.          */
//         /***********************************************************/
//         if (rc == 0)
//         {
//             printf("  poll() timed out.  End program.\n");
//             break;
//         }

//         /***********************************************************/
//         /* One or more descriptors are readable.  Need to          */
//         /* determine which ones they are.                          */
//         /***********************************************************/
//         current_size = nfds;
//         for (i = 0; i < current_size; i++)
//         {
//             /*********************************************************/
//             /* Loop through to find the descriptors that returned    */
//             /* POLLIN and determine whether it's the listening       */
//             /* or the active connection.                             */
//             /*********************************************************/
//             if (fds[i].revents == 0)
//                 continue;

//             /*********************************************************/
//             /* If revents is not POLLIN, it's an unexpected result,  */
//             /* log and end the server.                               */
//             /*********************************************************/
//             if (fds[i].revents != POLLIN)
//             {
//                 printf("  Error! revents = %d\n", fds[i].revents);
//                 end_server = TRUE;
//                 break;
//             }
//             if (fds[i].fd == listen_sd)
//             {
//                 /*******************************************************/
//                 /* Listening descriptor is readable.                   */
//                 /*******************************************************/
//                 printf("  Listening socket is readable\n");

//                 /*******************************************************/
//                 /* Accept all incoming connections that are            */
//                 /* queued up on the listening socket before we         */
//                 /* loop back and call poll again.                      */
//                 /*******************************************************/
//                 do
//                 {
//                     /*****************************************************/
//                     /* Accept each incoming connection. If               */
//                     /* accept fails with EWOULDBLOCK, then we            */
//                     /* have accepted all of them. Any other              */
//                     /* failure on accept will cause us to end the        */
//                     /* server.                                           */
//                     /*****************************************************/
//                     new_sd = accept(listen_sd, NULL, NULL);
//                     if (new_sd < 0)
//                     {
//                         if (errno != EWOULDBLOCK)
//                         {
//                             perror("  accept() failed");
//                             end_server = TRUE;
//                         }
//                         break;
//                     }

//                     /*****************************************************/
//                     /* Add the new incoming connection to the            */
//                     /* pollfd structure                                  */
//                     /*****************************************************/
//                     printf("  New incoming connection - %d\n", new_sd);
//                     fds[nfds].fd = new_sd;
//                     fds[nfds].events = POLLIN;
//                     nfds++;

//                     /*****************************************************/
//                     /* Loop back up and accept another incoming          */
//                     /* connection                                        */
//                     /*****************************************************/
//                 } while (new_sd != -1);
//             }

//             /*********************************************************/
//             /* This is not the listening socket, therefore an        */
//             /* existing connection must be readable                  */
//             /*********************************************************/

//             else
//             {
//                 printf("  Descriptor %d is readable\n", fds[i].fd);
//                 close_conn = FALSE;
//                 /*******************************************************/
//                 /* Receive all incoming data on this socket            */
//                 /* before we loop back and call poll again.            */
//                 /*******************************************************/

//                 do
//                 {
//                     /*****************************************************/
//                     /* Receive data on this connection until the         */
//                     /* recv fails with EWOULDBLOCK. If any other         */
//                     /* failure occurs, we will close the                 */
//                     /* connection.                                       */
//                     /*****************************************************/
//                     rc = recv(fds[i].fd, buffer, sizeof(buffer), 0);
//                     if (rc < 0)
//                     {
//                         if (errno != EWOULDBLOCK)
//                         {
//                             perror("  recv() failed");
//                             close_conn = TRUE;
//                         }
//                         break;
//                     }

//                     /*****************************************************/
//                     /* Check to see if the connection has been           */
//                     /* closed by the client                              */
//                     /*****************************************************/
//                     if (rc == 0)
//                     {
//                         printf("  Connection closed\n");
//                         close_conn = TRUE;
//                         break;
//                     }

//                     /*****************************************************/
//                     /* Data was received                                 */
//                     /*****************************************************/
//                     len = rc;
//                     printf("  %d bytes received\n", len);

//                     /*****************************************************/
//                     /* Echo the data back to the client                  */
//                     /*****************************************************/
//                     rc = send(fds[i].fd, buffer, len, 0);
//                     if (rc < 0)
//                     {
//                         perror("  send() failed");
//                         close_conn = TRUE;
//                         break;
//                     }

//                 } while (TRUE);

//                 /*******************************************************/
//                 /* If the close_conn flag was turned on, we need       */
//                 /* to clean up this active connection. This            */
//                 /* clean up process includes removing the              */
//                 /* descriptor.                                         */
//                 /*******************************************************/
//                 if (close_conn)
//                 {
//                     close(fds[i].fd);
//                     fds[i].fd = -1;
//                     compress_array = TRUE;
//                 }

//             } /* End of existing connection is readable             */
//         }     /* End of loop through pollable descriptors              */

//         /***********************************************************/
//         /* If the compress_array flag was turned on, we need       */
//         /* to squeeze together the array and decrement the number  */
//         /* of file descriptors. We do not need to move back the    */
//         /* events and revents fields because the events will always*/
//         /* be POLLIN in this case, and revents is output.          */
//         /***********************************************************/
//         if (compress_array)
//         {
//             compress_array = FALSE;
//             for (i = 0; i < nfds; i++)
//             {
//                 if (fds[i].fd == -1)
//                 {
//                     for (j = i; j < nfds; j++)
//                     {
//                         fds[j].fd = fds[j + 1].fd;
//                     }
//                     i--;
//                     nfds--;
//                 }
//             }
//         }

//     } while (end_server == FALSE); /* End of serving running.    */

//     /*************************************************************/
//     /* Clean up all of the sockets that are open                 */
//     /*************************************************************/
//     for (i = 0; i < nfds; i++)
//     {
//         if (fds[i].fd >= 0)
//             close(fds[i].fd);
//     }
// }
